package edu.indiana.d2i.vmm.policy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import edu.indiana.d2i.vmm.cloud.CloudInstanceInfo;
import edu.indiana.d2i.vmm.job.SigiriHTRCJobRequest;
import edu.indiana.d2i.vmm.vm.BasicHTRCVMController;
import edu.indiana.extreme.sigiri.JobInfoBean;

public class DynamicPoolPolicy implements VMManagementPolicy {
	private static final Log logger = LogFactory
			.getLog(DynamicPoolPolicy.class);

	private final ShortRunVMMgrPolicy shortRunPolicy;
	private AvailableVMPool availableVMs;

	// just work for the two methods overided!!!
	static class UniqueList<E> extends ArrayList<E> {
		private static final long serialVersionUID = 1L;
		private Set<E> set = new HashSet<E>();

		@Override
		public void add(int index, E element) {
			if (set.contains(element)) {
				return;
			} else {
				set.add(element);
				super.add(index, element);
			}
		}

		@Override
		public boolean add(E e) {
			if (set.contains(e))
				return false;
			else {
				set.add(e);
				return super.add(e);
			}
		}
	}

	final class AvailableVMPool {
		// <software_name, image_id>
		private Map<String, UniqueList<String>> software2Image = new HashMap<String, UniqueList<String>>();
		// <image_id, <image_type, list_of_vms>>
		private Map<String, Map<String, LinkedList<CloudInstanceInfo>>> image2Instance = new HashMap<String, Map<String, LinkedList<CloudInstanceInfo>>>();

		public List<CloudInstanceInfo> getVMs(String softwareFramework,
				String imageType, int instanceNum) {
			List<CloudInstanceInfo> response = null;
			synchronized (software2Image) {
				if (software2Image.containsKey(softwareFramework)) {
					UniqueList<String> idlist = software2Image
							.get(softwareFramework);
					synchronized (image2Instance) {
						for (String imageId : idlist) {
							Map<String, LinkedList<CloudInstanceInfo>> instances = image2Instance
									.get(imageId);
							LinkedList<CloudInstanceInfo> instancesFit = instances
									.get(imageType);
							if (instancesFit.size() >= instanceNum) {
								response = new ArrayList<CloudInstanceInfo>();
								for (int i = 0; i < instanceNum; i++)
									response.add(instancesFit.poll());
								break;
							}
						}
					}
				}
			}
			
			return response;
		}

		public void returnVMs2Pool(String softwareFramework,
				List<CloudInstanceInfo> vms) {
			UniqueList<String> imageIDs = null;
			synchronized (software2Image) {
				if (software2Image.containsKey(softwareFramework))
					imageIDs = software2Image.get(softwareFramework);
				else {
					imageIDs = new UniqueList<String>();
					software2Image.put(softwareFramework, imageIDs);
				}
			}

			synchronized (image2Instance) {
				for (CloudInstanceInfo cloudInstanceInfo : vms) {
					imageIDs.add(cloudInstanceInfo.imageID);

					if (image2Instance.containsKey(cloudInstanceInfo.imageID)) {
						Map<String, LinkedList<CloudInstanceInfo>> typeList = image2Instance
								.get(cloudInstanceInfo.imageID);
						if (typeList.containsKey(cloudInstanceInfo.vmType))
							typeList.get(cloudInstanceInfo.vmType).add(
									cloudInstanceInfo);
						else {
							LinkedList<CloudInstanceInfo> vmlist = new LinkedList<CloudInstanceInfo>();
							vmlist.add(cloudInstanceInfo);
							typeList.put(cloudInstanceInfo.vmType, vmlist);
						}
					} else {
						LinkedList<CloudInstanceInfo> vmlist = new LinkedList<CloudInstanceInfo>();
						vmlist.add(cloudInstanceInfo);
						Map<String, LinkedList<CloudInstanceInfo>> typeList = new HashMap<String, LinkedList<CloudInstanceInfo>>();
						typeList.put(cloudInstanceInfo.vmType, vmlist);
						image2Instance.put(cloudInstanceInfo.imageID, typeList);
					}
				}
			}
		}
	}

	public DynamicPoolPolicy(ShortRunVMMgrPolicy shortRunPolicy) {
		this.shortRunPolicy = shortRunPolicy;
		this.availableVMs = new AvailableVMPool();
	}

	@Override
	public BasicHTRCVMController allocateVMs(JobInfoBean jobInfo) throws Exception {
		// get VMs from the pool
		BasicHTRCVMController vmController = null;
		SigiriHTRCJobRequest jobRequest = SigiriHTRCJobRequest
				.parseFromXML(jobInfo.getJobDescription());
		List<CloudInstanceInfo> vms = availableVMs.getVMs(
				jobRequest.softwareFrameWork, jobRequest.imageType,
				jobRequest.instanceNum);
		if (vms != null) {
			vmController = new BasicHTRCVMController(this,
					jobRequest.softwareFrameWork, vms);
			logger.info("Able to find VMs from pool for job "
					+ jobInfo.getInternalId());
		}

		// allocate VMs from provider
		if (vms == null) {
			logger.info("Unable to find VMs from pool for job "
					+ jobInfo.getInternalId()
					+ ", try to allocate VMs from provider");
			vmController = shortRunPolicy.allocateVMs(jobInfo);
			if (vmController != null) vmController.setVMPolicy(this);
		}

		// if both fails, fail this request
		return vmController;
	}

	@Override
	public void releaseVMs(BasicHTRCVMController vmController) throws Exception {
		// release VMs to the pool
		availableVMs.returnVMs2Pool(vmController.getSoftwareFrameworkName(),
				vmController.getVMInfo());

		// if pool size exceeds a threshold, shutdown some VMs
		
	}
}
